package com.tywj.lib.net.retrofit.request

import androidx.collection.ArrayMap
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.tywj.lib.core.common.extension.applyScheduler
import com.tywj.lib.core.common.util.LogUtil
import com.tywj.lib.core.common.util.ResourcesUtil
import com.tywj.lib.lifecycle.extension.attachToLifecycle
import com.tywj.lib.net.retrofit.HttpUtil
import com.tywj.lib.net.retrofit.R
import com.tywj.lib.net.retrofit.callback.BaseCallback
import com.tywj.lib.net.retrofit.callback.HeadRequestCallback
import com.tywj.lib.net.retrofit.exception.ServerException
import com.tywj.lib.net.retrofit.model.HttpHeader
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import okhttp3.MediaType
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Response
import java.io.File
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.URLConnection
import java.net.UnknownHostException


/**
 * <pre>
 *     author : June Yang
 *     time   : 2020/5/8
 *     desc   : 网络请求基类
 *     version: 1.0.0
 * </pre>
 */
abstract class BaseRequest<N : BaseRequest<N>>(protected val url: String) {

    companion object {
        val userAgent = HttpHeader.getUserAgent()

        val MEDIA_TYPE_STREAM = MediaType.parse("application/octet-stream")!!
        val MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8")

        /**
         * 错误类型
         */
        const val ERROR_NET = -1
        const val ERROR_CONNECT = -2
        const val ERROR_TIMEOUT = -3
        const val ERROR_SERVER = -4
        const val ERROR_DATA = -5
        const val ERROR_HANDLE = -6
        const val ERROR_UNKNOWN = -7
    }

    // 请求header
    protected val headers = ArrayMap<String, String>()

    // 请求参数
    protected val params = ArrayMap<String, String>()

    // 生命周期所有者
    var lifecycleOwner: LifecycleOwner? = null
        private set

    // 是否为head请求
    protected var isHeadRequest = false

    @Suppress("UNCHECKED_CAST")
    fun header(key: String, value: String): N {
        headers[key] = value
        return this as N
    }

    @Suppress("UNCHECKED_CAST")
    open fun param(key: String, value: String): N {
        params[key] = value
        return this as N
    }

    /**
     * 设置实现了LifecycleOwner的子类
     * @param owner 实现了LifecycleOwner的子类，非必传
     * 如果设置了该字段，那么只能在[Lifecycle.State.DESTROYED]之前发起网络请求，
     * 如果在网络请求的过程中生命周期到了[Lifecycle.State.DESTROYED]，将会自动取消执行网络请求
     * 如果不设置该字段，网络请求会一直进行下去，直到请求完成
     */
    @Suppress("UNCHECKED_CAST")
    fun attachToLifecycle(owner: LifecycleOwner): N {
        lifecycleOwner = owner
        return this as N
    }

    /**
     * 连接超时时间
     * @param millis 单位是毫秒
     */
    @Suppress("UNCHECKED_CAST")
    fun connectTimeoutMillis(millis: Long): N {
        if (millis <= 0) {
            throw IllegalArgumentException("connect timeout must Greater than 0")
        }
        header(HttpHeader.HEAD_SINGLE_REQUEST_CONNECT_TIMEOUT, millis.toString())
        return this as N
    }

    /**
     * 读取超时时间
     * @param millis 单位是毫秒
     */
    @Suppress("UNCHECKED_CAST")
    fun readTimeoutMillis(millis: Long): N {
        if (millis <= 0) {
            throw IllegalArgumentException("read timeout must Greater than 0")
        }
        header(HttpHeader.HEAD_SINGLE_REQUEST_READ_TIMEOUT, millis.toString())
        return this as N
    }

    /**
     * 写入超时时间
     * @param millis 单位是毫秒
     */
    @Suppress("UNCHECKED_CAST")
    fun writeTimeoutMillis(millis: Long): N {
        if (millis <= 0) {
            throw IllegalArgumentException("write timeout must Greater than 0")
        }
        header(HttpHeader.HEAD_SINGLE_REQUEST_WRITE_TIMEOUT, millis.toString())
        return this as N
    }

    /**
     * 异步执行网络请求
     * @return 用于解除订阅
     */
    open fun <T> execute(callback: BaseCallback<T>): Disposable? {
        // 生命周期所有者不为null且生命周期已经处于销毁状态，那么不执行网络请求
        if (lifecycleOwner != null && lifecycleOwner!!.lifecycle.currentState == Lifecycle.State.DESTROYED) {
            return null
        }

        // 如果是head请求，那么只能使用HeadRequestCallback
        if (isHeadRequest) {
            if (callback !is HeadRequestCallback) {
                throw IllegalArgumentException("Head Request should only use HeadRequestCallback")
            }
        }

        checkHeadersAndParams()
        callback.request = this

        // 执行网络请求
        val disposable = getRequestMethod(callback)
            .map {
                if (it.isSuccessful) {
                    callback.convertResponse(it)
                } else {
                    throw ServerException(it.message())
                }
            }
            .applyScheduler()
            .subscribe({
                try {
                    callback.onSuccess(it!!)
                } catch (e: Exception) {
                    LogUtil.printStackTrace(e)
                    callback.onError(ERROR_HANDLE, ResourcesUtil.getString(R.string.net_retrofit_error_handle))
                } finally {
                    callback.onComplete()
                }
            }, {
                try {
                    LogUtil.printStackTrace(it)
                    handleRequestError(callback, it as Exception)
                } catch (e: Exception) {
                    LogUtil.printStackTrace(e)
                    callback.onError(ERROR_HANDLE, ResourcesUtil.getString(R.string.net_retrofit_error_handle))
                } finally {
                    callback.onComplete()
                }
            })

        // 当生命周期所有者不为null，监听生命周期变化，如果生命周期走到onDestroy，取消网络请求
        lifecycleOwner?.let { disposable.attachToLifecycle(it) }

        return disposable
    }

    /**
     * 自行处理网络请求
     */
    fun execute(): Observable<Response<ResponseBody>> {
        checkHeadersAndParams()
        return getRequestMethod(null)
    }

    abstract fun getRequestMethod(callback: BaseCallback<*>?): Observable<Response<ResponseBody>>

    protected fun toRequestBody(file: File): RequestBody {
        return RequestBody.create(guessMimeType(file.name), file)
    }

    protected open fun checkHeadersAndParams() {
        // 如果用户没有设置userAgent，那么设置默认的userAgent
        if (!headers.containsKey(HttpHeader.HEAD_KEY_USER_AGENT)) {
            headers[HttpHeader.HEAD_KEY_USER_AGENT] = userAgent
        }

        // 设置通用请求头和请求参数
        HttpUtil.httpConfig.commonHeaders.entries.forEach { header(it.key, it.value) }
        HttpUtil.httpConfig.commonParams.entries.forEach { param(it.key, it.value) }
    }

    private fun handleRequestError(callback: BaseCallback<*>, e: Exception) {
        when (e) {
            is UnknownHostException -> callback.onError(
                ERROR_NET,
                ResourcesUtil.getString(R.string.net_retrofit_error_net)
            )
            is ConnectException -> callback.onError(
                ERROR_CONNECT,
                ResourcesUtil.getString(R.string.net_retrofit_error_connect)
            )
            is SocketTimeoutException -> callback.onError(
                ERROR_TIMEOUT,
                ResourcesUtil.getString(R.string.net_retrofit_error_timeout)
            )
            is ServerException -> {
                if (e.message == null || e.message!!.isEmpty()) {
                    callback.onError(ERROR_SERVER, ResourcesUtil.getString(R.string.net_retrofit_error_server))
                } else {
                    callback.onError(ERROR_SERVER, e.message!!)
                }
            }
            is NullPointerException -> callback.onError(
                ERROR_DATA,
                ResourcesUtil.getString(R.string.net_retrofit_error_data)
            )
            else -> callback.onError(ERROR_UNKNOWN, ResourcesUtil.getString(R.string.net_retrofit_error_unknown))
        }
    }

    private fun guessMimeType(fileName: String): MediaType {
        // 解决文件名中含有#号异常的问题
        val name = fileName.replace("#", "")
        val fileNameMap = URLConnection.getFileNameMap()
        val contentType = fileNameMap.getContentTypeFor(name) ?: return MEDIA_TYPE_STREAM
        return MediaType.parse(contentType) ?: return MEDIA_TYPE_STREAM
    }
}